feat: Support multi-module Java projects in tasks#244
Conversation
|
Thanks for the PR, could you provide steps to test these changes or add how you tested them? |
5b53433 to
0e45c2c
Compare
Previously, Zed's Java tasks for running and testing applications did not correctly support multi-module Maven or Gradle projects. When executing a task from a file within a submodule, the commands would attempt to run at the root level, potentially leading to incorrect execution or failures. This change introduces logic to dynamically determine the nearest Maven `pom.xml` or Gradle `build.gradle`/`settings.gradle` file relative to the currently active file. It then modifies the Maven and Gradle commands to target the specific module. For Maven, this involves adding the `-pl <module>` and `-am` flags. For Gradle, it prepends the module path (e.g., `:module-name:`) to the `run` or `test` command. This ensures that tasks are executed within the context of the correct module, improving the developer experience for multi-module Java projects.
0e45c2c to
bb03226
Compare
|
Hi, I have added integration tests to this project (I wasn't sure if these are allowed/expected for extensions). In addition, I apologize that the original PR had an issue with the Maven part; I have fixed it. I also tested the changes in tasks.json by adding them as custom tasks in my local Zed config. I tried it out on a complex Gradle project with composite and multiple modules, and a Maven project with multiple modules. |
There was a problem hiding this comment.
I've never thought of creating a temp project like you are doing here. This is a really nice idea to make testing more robust. I would although expand the temp projects to cover all instances covered by our tasks, for example nesting and single module project
There was a problem hiding this comment.
Thanks. Some are missing still though:
- java-test-all for Maven
- java-test-class
Let's include them as well
There was a problem hiding this comment.
Okay, as the tests are growing, I have refactored the integration tests. I introduced a builder pattern for the TestProject and TaskRunner so the test methods can focus on what to test instead of setting up the test.
I hope I didn't miss testing any branches!
| { | ||
| "label": "Run $ZED_CUSTOM_java_class_name", | ||
| "command": "pkg=\"${ZED_CUSTOM_java_package_name:}\"; cls=\"$ZED_CUSTOM_java_class_name\"; if [ -n \"$pkg\" ]; then c=\"$pkg.$cls\"; else c=\"$cls\"; fi; if [ -f pom.xml ]; then [ -f ./mvnw ] && CMD=\"./mvnw\" || CMD=\"mvn\"; $CMD clean compile exec:java -Dexec.mainClass=\"$c\"; elif [ -f build.gradle ] || [ -f build.gradle.kts ]; then [ -f ./gradlew ] && CMD=\"./gradlew\" || CMD=\"gradle\"; $CMD run -PmainClass=\"$c\"; else find . -name '*.java' -not -path './bin/*' -not -path './target/*' -not -path './build/*' -print0 | xargs -0 javac -d bin && java -cp bin \"$c\"; fi;", | ||
| "command": "pkg=\"$ZED_CUSTOM_java_package_name\"; cls=\"$ZED_CUSTOM_java_class_name\"; if [ -n \"$pkg\" ]; then c=\"$pkg.$cls\"; else c=\"$cls\"; fi; f=\"$ZED_FILE\"; p=\"$PWD\"; d=$(dirname \"${f#$p/}\"); if [ -f pom.xml ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/pom.xml\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; [ -f ./mvnw ] && CMD=\"./mvnw\" || CMD=\"mvn\"; if [ \"$m\" = \".\" ]; then $CMD clean test-compile exec:java -Dexec.mainClass=\"$c\" -Dexec.classpathScope=test; else $CMD clean test-compile -pl \"$m\" -am && $CMD exec:java -pl \"$m\" -Dexec.mainClass=\"$c\" -Dexec.classpathScope=test; fi; elif [ -f build.gradle ] || [ -f build.gradle.kts ] || [ -f settings.gradle ] || [ -f settings.gradle.kts ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/build.gradle\" ] || [ -f \"$md/build.gradle.kts\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; if [ \"$m\" = \".\" ]; then mp=\"\"; else mp=\":$(echo \"$m\" | tr '/' ':')\"; fi; [ -f ./gradlew ] && CMD=\"./gradlew\" || CMD=\"gradle\"; $CMD ${mp}:run -PmainClass=\"$c\"; else find . -name '*.java' -not -path './bin/*' -not -path './target/*' -not -path './build/*' -print0 | xargs -0 javac -d bin && java -cp bin \"$c\"; fi;", |
There was a problem hiding this comment.
Why are we using test-compile for running the main class alongside providing the tests' scope?
There was a problem hiding this comment.
The switch to test-compile and the test classpath scope increases flexibility. While the previous version only supported classes in src/main/java, this update allows users to run main methods or classes located within the test directory as well.
There was a problem hiding this comment.
I see your point but I've rarely seen such situation where a main method is located in the test directory.
If we are going to keep this, let's at least compile-test only when effectively in such situation. If I'm testing my main method I would not love getting slowed down by compiling the tests as well.
There was a problem hiding this comment.
You're right, we definitely want to avoid that slowdown. I've added a check so it strictly defaults to compile for normal files, and only falls back to test-compile if you are running a file that is specifically inside the src/test/ directory.
| { | ||
| "label": "Run tests", | ||
| "command": "if [ -f pom.xml ]; then [ -f ./mvnw ] && CMD=\"./mvnw\" || CMD=\"mvn\"; $CMD clean test; elif [ -f build.gradle ] || [ -f build.gradle.kts ]; then [ -f ./gradlew ] && CMD=\"./gradlew\" || CMD=\"gradle\"; $CMD test; else >&2 echo 'No build tool found'; exit 1; fi;", | ||
| "command": "f=\"$ZED_FILE\"; p=\"$PWD\"; d=$(dirname \"${f#$p/}\"); if [ -f pom.xml ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/pom.xml\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; [ -f ./mvnw ] && CMD=\"./mvnw\" || CMD=\"mvn\"; if [ \"$m\" = \".\" ]; then $CMD clean test; else $CMD clean test-compile -pl \"$m\" -am && $CMD test -pl \"$m\"; fi; elif [ -f build.gradle ] || [ -f build.gradle.kts ] || [ -f settings.gradle ] || [ -f settings.gradle.kts ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/build.gradle\" ] || [ -f \"$md/build.gradle.kts\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; if [ \"$m\" = \".\" ]; then mp=\"\"; else mp=\":$(echo \"$m\" | tr '/' ':')\"; fi; [ -f ./gradlew ] && CMD=\"./gradlew\" || CMD=\"gradle\"; $CMD ${mp}:test; else >&2 echo 'No build tool found'; exit 1; fi;", |
There was a problem hiding this comment.
Why are we first compiling the test to the run it for maven? Isn't that redundant?
There was a problem hiding this comment.
It's a workaround for Maven multi-module limitations. Since one module's tests may depend on another's, we must force a full build (test-compile -am) first. This ensures all dependencies are ready without wasting time running tests for every sub-module, which Maven (unlike Gradle) can't handle in one step.
Introduces a suite of integration tests for Java task commands across various project configurations and build tools. This includes: - Added to tasks in to allow programmatic identification and testing of commands. - Refactored to support flexible module paths. - Introduced to retrieve task commands robustly by their tags. - Added new tests for Maven projects covering single, multi-module, and nested module execution, and nested class test methods. - Added new tests for Gradle projects covering single, multi-module, and nested module execution, and running all tests.
Update the Java task runner to detect if the current file is inside a
test directory ('src/test/'). Depending on the file location, Maven
will now dynamically select 'test-compile' and 'test' classpath scope
or 'compile' and 'runtime' scope. This ensures that main classes are
executed with the correct dependencies and build targets.
Additionally, the Rust test suite has been heavily refactored. It now
utilizes a builder pattern (TestProject and TaskRunner) to simplify
setup, improve readability, and add test coverage for both runtime
and test execution paths across Maven and Gradle.
Previously, Zed's Java tasks for running and testing applications did not correctly support multi-module Maven or Gradle projects. When executing a task from a file within a submodule, the commands would attempt to run at the root level, potentially leading to incorrect execution or failures.
This change introduces logic to dynamically determine the nearest Maven
pom.xmlor Gradlebuild.gradle/settings.gradlefile relative to the currently active file. It then modifies the Maven and Gradle commands to target the specific module.For Maven, this involves adding the
-pl <module>and-amflags. For Gradle, it prepends the module path (e.g.,:module-name:) to therunortestcommand. This ensures that tasks are executed within the context of the correct module, improving the developer experience for multi-module Java projects.